﻿package com.effects {
	
	import com.events.TweenEvent;

	import flash.utils.getTimer;
	import flash.utils.Timer;	
	
	import flash.events.TimerEvent;
	import flash.events.EventDispatcher;
	
	/**
	* Classe para interpolação numerica de um ou vários valores.
	* Pode ser utilzada como base para animações dinâmicas, interpolação de filtros, etc.
	* @author Ricardo Teixeira
	* @since 01/05/2008
	* @version 1.0
	*/
	public class Tween extends EventDispatcher{
		
		public static var ACTIVE_TWEENS:Array = new Array();
		public static var INTERVAL : Number = 10;
		public static var INTERVAL_TOKEN : Timer;
		public static var DISPATCHER : Object = new Object();
						
		private var _id:int;
		private var _initVal:*; // relaxed type to accommodate numbers or arrays
		private var _endVal:*;
		private var _duration : Number;
		private var _startTime : Number;
		private var arrayMode : Boolean;
		private var _easingEquation : Function;
		private var _afected:*;
		

		/**
		 * Método que adiciona o Tween especificado à lista de Tween's.
		 * @method	addTween 
		 * @param	index
		 * @return	void
		 */
		public static function addTween(tween : Tween) : void {
			tween.id = ACTIVE_TWEENS.length;
			ACTIVE_TWEENS.push(tween);
					
			if (INTERVAL_TOKEN == null) {
				//Dispatcher.onDispatchTweens = onDispatchTweens;
				INTERVAL_TOKEN = new Timer (INTERVAL); // setInterval(Dispatcher, "onDispatchTweens", Interval);
				INTERVAL_TOKEN.start ();
				INTERVAL_TOKEN.addEventListener (TimerEvent.TIMER, onDispatchTweens);
			}
		}
		
		/**
		 * Método que remove e pára o Tween de index especificado.
		 * @method	removeTweenAt 
		 * @param	index
		 * @return	void
		 */
		public static function removeTweenAt(index : Number) : void {
			var aT:Array = ACTIVE_TWEENS;

			if (index >= aT.length || index < 0 || isNaN (index)) {
				return;
			}

			aT.splice(index, 1);
			var len:int = aT.length;
			for (var i:int = index; i < len; i++) {
				aT[i].id--;
			}
			if (len == 0) {
				INTERVAL_TOKEN.stop ();
				INTERVAL_TOKEN.removeEventListener (TimerEvent.TIMER, onDispatchTweens);
				INTERVAL_TOKEN = null;
			}
		}
		
		/**
		 * @method	removeAllTweens 
		 */
		public static function removeAllTweens () : void {
			var aT:Array = ACTIVE_TWEENS;
			var index:int = 0;
			aT.splice(index, 1);
			var len:int = aT.length;
			for (var i:int = index; i < len; i++) {
				aT[i].id--;
			}
			INTERVAL_TOKEN.stop ();
			INTERVAL_TOKEN.removeEventListener (TimerEvent.TIMER, onDispatchTweens);
			INTERVAL_TOKEN = null;
		}
		
		/**
		 * Método manipulador de evento TIMER.
		 * @method	onDispatchTweens 
		 * @param	evt TimerEvent recebido no manipulador de evento.
		 * @return	void
		 */
		public static function onDispatchTweens (evt:TimerEvent) : void {
			try {
				var aT:Array = ACTIVE_TWEENS;
				var len:int = aT.length;
				for (var i:int = 0; i < len; i++) {
					aT[i].doInterval();
				}				
				evt.updateAfterEvent ();
			} catch (e:Error) {
				trace ("Error 001: Uma variável do tipo Tween perdeu a referência para o objeto. Talvez a validação como segue resolva o problema.\nif (meuTween != null) Tween.removeTweenAt (meuTween.id);");				
			}
			
		}
		
		/**
		 * Tween	Construtor
		 * @param	init
		 * @param	end
		 * @param	dur
		 */
		public function Tween (init:*, end:*, dur:Number = 1000, afected:* = null) {
			if (typeof init != "number") {
				this.arrayMode = true;
			} else {
				this.arrayMode = false;
			}
			this.afected = afected;
			this._initVal = init;
			this._endVal = end;
			this._duration = dur;
			this._easingEquation = this.easingEquationDefault;

			this._startTime = getTimer();

			if ( this._duration == 0 ) {
				this.endTween(); //doInterval() this called easingEq which got a div/by/zero
			} else {
				Tween.addTween(this);
			}
		}
		
		//{ region Methods
		
		/**
		* Método dispacha o evento TWEEN_UPDATE ou  finaliza o Tween.
		 * @mehotd doInterval
		 */
		public function doInterval():void {
			var curTime:Number = getTimer() - this._startTime;
			var curVal:* = this.getCurVal(curTime);
			
			if (curTime >= this._duration) {
				this.endTween();
			} else {
				var evt:TweenEvent = new TweenEvent (TweenEvent.TWEEN_UPDATE, curVal, this.afected);
				this.dispatchEvent (evt);
			}
		}

		/**
		 * @method	getCurVal
		 * @param	curTime
		 * @return	* (Number or Array)
		 */
		private function getCurVal(curTime:Number):* {
			var result:*;
			if (this.arrayMode) {
				var returnArray:Array = new Array();
				for (var i:int = 0; i < this._initVal.length; i++) {
					returnArray[i] = this.easingEquation(curTime, this._initVal[i], this._endVal[i] - this._initVal[i], this._duration);
					
				}
				result = returnArray;
			} else {
				result = this.easingEquation(curTime, this._initVal, this._endVal - this._initVal, this._duration);
			}
			
			return result;
		}
		
		/**
		 * Método dispacha o evento TWEEN_END.
		 * @method	endTween
		 */
		public function endTween():void {
			var evt:TweenEvent = new TweenEvent (TweenEvent.TWEEN_END, this._endVal, this.afected);
			this.dispatchEvent (evt);
			Tween.removeTweenAt(this._id);
		}

		/**
		 * Método que representa a equação de ease da interpolação.
		 * Padrão para a função seno.
		 * @method	easingEquationDefault
		 * @param	t
		 * @param	b
		 * @param	c
		 * @param	d
		 * @return
		 */
		public function easingEquationDefault(t:Number, b:Number, c:Number, d:Number):Number {
			return c/2 * ( Math.sin( Math.PI * (t/d - 0.5) ) + 1 ) + b;
		}
				
		//} endregion
		
		//{ region Events
		
		/**
		 * Leitura
		 * @property	duration
		 */
		public function get duration ():Number {
			return this._duration;
		}
		
		/**
		 * Escrita
		 * @property	duration
		 */
		public function set duration (v:Number):void {
			this._duration= v;
		}
		
		/**
		 * Leitura
		 * @property	initVal
		 */
		public function get initVal ():* {
			return this._initVal;
		}
		
		/**
		 * Escrita
		 * @property	initVal
		 */
		public function set initVal (v:*):void {
			this._initVal= v;
		}
		
		/**
		 * Leitura
		 * @property	endVal
		 */
		public function get endVal ():* {
			return this._endVal;
		}
		
		/**
		 * Escrita
		 * @property	endVal
		 */
		public function set endVal(v:*):void {
			this._endVal= v;
		}
		
		/**
		 * Leitura
		 * @property	id
		 */
		public function get id ():int {
			return this._id;
		}
		
		/**
		 * Escrita
		 * @property	id
		 */
		public function set id (i:int):void {
			this._id = i;
		}
		
		/**
		 * Leitura
		 * @property	easingEquation
		 */
		public function get easingEquation ():Function {
			return this._easingEquation;
		}
		
		/**
		 * Escrita
		 * @property	easingEquation
		 */
		public function set easingEquation  (i:Function):void {
			this._easingEquation   = i;
		}
		
		public function get afected():* { return _afected; }
		
		public function set afected(value:*):void {
			_afected = value;
		}
		
		//} endregion
	}
	
}